package com.tinusgraglin.enclusiv.datalayer.datasources

import com.squareup.moshi.JsonDataException
import com.tinusgraglin.enclusiv.datalayer.BasicUserProfile
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException
import com.tinusgraglin.enclusiv.datalayer.Exceptions.*
import com.tinusgraglin.enclusiv.datalayer.RemoteSourceCommunication
import com.tinusgraglin.enclusiv.datalayer.RemoteSourceCommunication.UnexpectedResultString
import com.tinusgraglin.enclusiv.datalayer.UserArticleStats
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File

class UserDataStatsRemoteSource(private val communication: RemoteSourceCommunication) {
    private val serverUrl = communication.serverUrl
    private val client = communication.client

    private val resultStrBasicUserProfileResponseAdapter =
        communication.newResultStrDataPairAdaptor<BasicUserProfile>(BasicUserProfile::class.java)

    private val resultStrUserArticleStatsResponseAdapter =
        communication.newResultStrDataPairAdaptor<UserArticleStats>(UserArticleStats::class.java)

    fun getUser(email: String, password: String): BasicUserProfile {
        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("account/login")
            .build()

        val requestBody = JSONObject().apply {
            put("email", email)
            put("password", password)
        }.toString()

        val request = Request.Builder()
            .url(requestUrl)
            .post(requestBody.toRequestBody("application/json".toMediaType()))
            .build()

        val resp = client.newCall(request).execute()

        if (!resp.isSuccessful) {
            throw IOException("Request Error: ${resp.code}")
        } else {
            val responseStr = resp.body.string()

            val pairResponse = resultStrBasicUserProfileResponseAdapter.fromJson(responseStr)
                ?: throw JsonDataException("Data conversion failed.")

            when (pairResponse.result.lowercase()) {
                "user not found" -> throw UserNotFound()
                "wrong password" -> throw WrongPassword()
                "success" -> return pairResponse.data!!
                else -> throw IllegalStateException("Unexpected Result String")
            }
        }
    }

    fun uploadProfileImg(uid: Long, file: File) {
        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("profile_img/upload")
            .build()

        val requestBody = MultipartBody.Builder()
            .addFormDataPart("image", file.name, file.asRequestBody())
            .addFormDataPart("id", uid.toString())
            .addFormDataPart("type", file.extension)
            .build()

        val request = Request.Builder()
            .url(requestUrl)
            .post(requestBody)
            .build()

        val resp = client.newCall(request).execute()

        if (!resp.isSuccessful) {
            throw IOException("Request Error: ${resp.code}")
        } else {
            val responseStr = resp.body.string()

            val pairResponse = communication.resultStrBoolDataResponseAdapter.fromJson(responseStr)
                ?: throw JsonDataException("Data conversion failed.")

            when (pairResponse.result) {
                "success" -> {}
                "failed" -> throw UploadFailure()
                else -> throw IllegalStateException("Unexpected Result String")
            }
        }
    }

    fun getProfileImg(uid: Long) : ByteArray {
        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("profile_img/image")
            .addQueryParameter("id", uid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)
        val data = responseBody.byteStream().readBytes()
        responseBody.byteStream().close()
        return data
    }

    enum class UserArticleRelation {
        OWN,
        LIKE,
        FAV,
        RECOMMENDATION,
        LISTENING
    }

    fun doUserArticleAction(uid: Long, aid: Long, action: UserArticleRelation, undo: Boolean) {
        val actionStr =
            when(action) {
                UserArticleRelation.LIKE -> if (undo) "dislike" else "like"
                UserArticleRelation.FAV -> if (undo) "cancel_collect" else "collect"
                else -> throw IllegalArgumentException()
            }

        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("action/${actionStr}")
            .addQueryParameter("user_id", uid.toString())
            .addQueryParameter("article_id", aid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)

        val pairResponse =
            communication.resultStrBoolDataResponseAdapter.fromJson(responseBody.string())
                ?: throw JsonDataException("Conversion Failed!")

        when (pairResponse.result.lowercase()) {
            "user not found" -> throw UserNotFound()
            "article not found" -> throw ArticleNotFound()
            "success" -> {}
            else -> throw UnexpectedResultString()
        }
    }

    fun userArticleStats(uid: Long, aid: Long) : UserArticleStats {
        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("article/article_stats")
            .addQueryParameter("user_id", uid.toString())
            .addQueryParameter("article_id", aid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)

        val pairResponse =
            resultStrUserArticleStatsResponseAdapter.fromJson(responseBody.string())
                ?: throw JsonDataException("Conversion Failed!")
        when (pairResponse.result) {
            "user not found" -> throw UserNotFound()
            "article not found" -> throw ArticleNotFound()
            "success" -> return pairResponse.data!!
            else -> throw UnexpectedResultString()
        }
    }

    fun fromUserGetArticles(uid: Long, relation: UserArticleRelation): List<Long> {
        val pathStr = when (relation) {
            UserArticleRelation.RECOMMENDATION -> "article/recommend"
            UserArticleRelation.OWN -> "article/article_id"
            UserArticleRelation.LIKE -> "action/like"
            UserArticleRelation.FAV -> "action/collect"
            UserArticleRelation.LISTENING -> ""
        }

        val queryParamName = when (relation) {
            UserArticleRelation.LIKE, UserArticleRelation.FAV -> "user_id"
            else -> "id"
        }

        val requestUrl = serverUrl.newBuilder()
            .addPathSegments(pathStr)
            .addQueryParameter(queryParamName, uid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)

        val pairResponse =
            communication.resultStrLongListResponseAdapter.fromJson(responseBody.string())
                ?: throw JsonDataException("Conversion Failed!")

        when (pairResponse.result.lowercase()) {
            "user not found" -> throw UserNotFound()
            "success" -> return pairResponse.data!!
            else  -> throw UnexpectedResultString()
        }
    }

    enum class UserUserAction {
        LISTEN,
        LISTENED_BY
    }

    fun doUserUserAction(subjectUid: Long, objectUid: Long, action: UserUserAction, undo: Boolean) {
        val actionStr = when(action) {
            UserUserAction.LISTEN -> if (undo) "cancel_subscribe" else "subscribe"
            else -> throw IllegalArgumentException("LISTENED_BY action is done automatically.")
        }

        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("action/${actionStr}")
            .addQueryParameter("user_id", subjectUid.toString())
            .addQueryParameter("author_id", objectUid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)

        val pairResponse =
            communication.resultStrBoolDataResponseAdapter.fromJson(responseBody.string())
                ?: throw JsonDataException("Conversion Failed!")

        when (pairResponse.result.lowercase()) {
            "user not found" -> throw UserNotFound()
            "author not found" -> throw UserNotFound()
            "success" -> {}
            else -> throw UnexpectedResultString()
        }

    }

    fun fromUserGetUsers(uid: Long, relatingAction: UserUserAction) : List<Long> {
        val actionStr = when(relatingAction) {
            UserUserAction.LISTEN -> "subscribes"
            UserUserAction.LISTENED_BY -> "followers"
        }

        val requestUrl = serverUrl.newBuilder()
            .addPathSegments("action/${actionStr}")
            .addQueryParameter("user_id", uid.toString())
            .build()

        val responseBody = communication.templateGet(requestUrl)

        val pairResponse =
            communication.resultStrLongListResponseAdapter.fromJson(responseBody.string())
                ?: throw JsonDataException("Conversion Failed!")

        when (pairResponse.result.lowercase()) {
            "user not found" -> throw UserNotFound()
            "success" -> return pairResponse.data!!
            else -> throw UnexpectedResultString()
        }
    }
}